package top.v5it.japi.plus.support;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import top.v5it.japi.plus.common.ConstantCommon;
import top.v5it.japi.plus.common.util.JackSonUtil;
import top.v5it.japi.plus.support.lock.RedisShardLock;

import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 加载缓存基类
 *
 * @author zhanpu
 * @date 2021/3/7
 */
@Slf4j
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class AbstractJapiLoadRedisSet<T extends Serializable> implements JapiLoadRedisSet {

    private final RedisTemplate<String, Object> redisTemplate;
    private final String cacheKey;
    private final String lockKey;
    private final String uniqueField;

    /**
     * 加载数据模板
     *
     * @param isThrow 加载数据，当没有数据是否抛出异常
     */
    @Override
    public void loadSet(final boolean isThrow) {

        if (Objects.equals(redisTemplate.hasKey(cacheKey), Boolean.TRUE)) {
            log.info("+++++一重检验：key[{}]数据已缓存，退出加载", cacheKey);
            return;
        }

        RedisShardLock lock = new RedisShardLock(lockKey, IdUtil.fastSimpleUUID(), redisTemplate);

        try {
            if (lock.lock(3000L)) {

                if (Objects.equals(redisTemplate.hasKey(cacheKey), Boolean.TRUE)) {
                    log.info("+++++二重检验：key[{}]数据已缓存，退出加载", cacheKey);
                    return;
                }

                Iterable<T> dataAll = dataAll();

                if (CollectionUtil.isEmpty(dataAll)) {
                    if (!isThrow) {
                        return;
                    }

                    throw new IllegalStateException(StrUtil.format("+++++key[{}]待缓存数据缺失，请先配置待缓存数据", cacheKey));
                }

                putAll(dataAll);
            }
        } finally {
            lock.unlock();
            lock = null;
        }
    }

    /**
     * 需要加载的数据
     *
     * @return 缓存数据
     */
    protected abstract Iterable<T> dataAll();

    /**
     * 批量加载缓存
     *
     * @param iterable 缓存数据
     */
    protected void putAll(Iterable<T> iterable) {
        BoundHashOperations<String, Object, Object> boundHashOps = redisTemplate.boundHashOps(cacheKey);

        // {key: value} -> {key:uniqueValue, value}
        Function<Map<String, String>, Map<String, String>> mapper = v -> v.entrySet()
                .stream()
                .collect(Collectors.toMap(s -> s.getKey() + ConstantCommon.COLON + v.get(uniqueField), Map.Entry::getValue));

        List<Map<String, String>> mapList =
                JackSonUtil.getMapper()
                        .convertValue(iterable, new TypeReference<List<Map<String, String>>>() {
                        });

        mapList.stream()
                .map(mapper)
                .iterator()
                .forEachRemaining(boundHashOps::putAll);
    }
}
